define('map3d.Net', ['H', 'encoding', 'RequestPool'], function (H, $e, RequestPool) {

    /*
     * Utils
     */

    function dataURItoBlob(dataURI) {
        // convert base64/URLEncoded data component to raw binary data held in a string
        var byteString;
        if (dataURI.split(',')[0].indexOf('base64') >= 0)
            byteString = atob(dataURI.split(',')[1]);
        else
            byteString = unescape(dataURI.split(',')[1]);

        // separate out the mime component
        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

        // write the bytes of the string to a typed array
        var ia = new Uint8Array(byteString.length);
        for (var i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
        }

        return new Blob([ia], {type: mimeString});
    }

    function compactParams(params) {

        //for binary content, do not do anything
        if (window.ArrayBuffer !== undefined && params instanceof ArrayBuffer) {
            return params;
        }
        if (window.Blob !== undefined && params instanceof Blob) {
            return params;
        }
        if (window.ArrayBuffer !== undefined && ArrayBuffer.prototype.isView && ArrayBuffer.isView(params)) {
            return params;
        }

        var s = [];
        var add = function (k, v) {
            s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);
        };

        H.each(params, function (val, key) {
            var newVal = val;
            if (H.isObject(val)) {
                newVal = JSON.stringify(val);
            }
            add(key, newVal);
        });

        return s.join('&').replace(/%20/g, '+');
    }

    function getUrl(url, params) {
        var req = '';
        if (params) {
            req = compactParams(params);
            if (req && req.length > 0) {
                req = '?' + req;
            }
        }
        return url + req;
    }

    /*
     * Workers
     */

    var workerPool = new RequestPool({
        useWorkers: true,
        maxConcurrent: navigator.hardwareConcurrency || 10,
        // workerUrl: 'lib/workers/xhr.js',
    });

    function ReqAsync(url, opts, callback, onprogress, errback) {
        var workerHandle;

        if (window.Worker) {

            // if using workers, resolve relative url now
            if (!url.startsWith('http')) {
                url = (new URL(url, document.location)).href;
            }

            workerHandle = workerPool._acquireWorker();

            workerHandle._executeReq({
                _url: url,
                _opts: opts,
            }, callback, errback, onprogress, function onFinish() {
                workerPool._returnWorkerToPool(workerHandle);
            });

            return {
                abort: function () {
                    workerHandle._abortReq();
                },
            };
        } else {
            return Req(url, opts, callback, onprogress, errback);
        }
    }

    var USE_WORKERS = window._USE_WORKERS;

    /*
     * Req
     */

    function Req(url, opts, callback, onprogress, errback) {

        var r = new XMLHttpRequest(opts || {});

        if (opts.withCredentials) {
            r.withCredentials = opts.withCredentials;
        }

        if (opts.timeout) {
            r.timeout = opts.timeout;
        }

        r.onreadystatechange = function () {
            //4 = finished
            if (r.readyState === 4) {
                if (r.status === 200) {
                    //200 OK
                    if (callback) {
                        callback(r.response || r.responseText, r);
                    }
                } else if (r.status >= 400) {
                    //4xx Error
                    if (errback) {
                        errback(r.response || r.responseText);
                    }
                } else if (r.status < 100) {
                    //internal error, request not sent
                    /*
                     * for example:
                     * header not permitted
                     */
                    if (errback) {
                        errback(r.status);
                    }
                }
                //30x redirection
            } else if (r.readyState === 3) {
                //3 = not finished
                if (onprogress) {
                    onprogress(r.response || r.responseText);
                }
            }
        };

        if (opts.uploadProgress) {
            r.upload.addEventListener('progress', function (e) {
                opts.uploadProgress(e.loaded, e.total);
            });
        }

        if (opts.dataType) {
            //IE doesn't support this
            var ieVer = H.getIE();
            if (ieVer > 11 || ieVer == 0) {
                r.responseType = opts.dataType;
            }
        }

        var method = opts.method || 'POST';

        r.open(method, url, true);

        if (opts.headers) {
            try {
                //this will not work in browser (cannot set custom header in preflight responses)
                opts.headers.forEach(function (headerPair) {
                    r.setRequestHeader(headerPair[0], headerPair[1]);
                });
            } catch (e) {
            }
        }

        return r;
    }

    function post(url, body, opts, callback) {

        opts.method = 'POST';
        var errback = opts.errback;
        var onprogress = opts.onprogress;

        // Not sending POST via workers

        // if (USE_WORKERS && window.Worker && !opts.forceSync) {
        //
        //     opts.method = 'POST';
        //     opts.body = body;
        //
        //     return ReqAsync(url, opts, callback, onprogress, errback);
        // }

        var req = Req(url, opts, callback, onprogress, errback);

        setTimeout(function () {
            if (body instanceof FormData) {
                //do nothing, boundary will be provided by default for FormData (multipart/form-data)
            } else {
                req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            }
            req.send(body);
        }, 0);

        return req;
    }

    function get(url, opts, callback) {
        // console.log('requesting url ' + url + " ...");

        opts.method = 'GET';
        var errback = opts.errback;
        var onprogress = opts.onprogress;

        if (USE_WORKERS && window.Worker && !opts.forceSync) {
            opts.maxConcurrent = opts.maxConcurrent || navigator.hardwareConcurrency || 10;
            return ReqAsync(url, opts, callback, onprogress, errback);
        }

        var req = Req(url, opts, callback, onprogress, errback);

        setTimeout(function () {
            req.send(null);
        }, 0);

        return req;
    }

    function getUniversal(url, callback, opts) {
        opts = opts || {};
        opts.method = 'GET';

        if (USE_WORKERS && window.Worker && !opts.forceSync) {
            opts.maxConcurrent = opts.maxConcurrent || navigator.hardwareConcurrency || 10;
            return ReqAsync(url, opts, callback);
        }

        var req = Req(url, opts, callback, opts.onprogress, callback);

        setTimeout(function () {
            req.send(null);
        }, 0);

        return req;
    }

    function decodeJson(resp) {
        var EMPTY_ARRAY = [];
        if (!resp) return EMPTY_ARRAY;
        if (resp instanceof ArrayBuffer) {
            resp = $e.utf82str(resp);
        }
        try {
            resp = JSON.parse(resp);
        } catch (e) {
        }
        return resp;
    }

    function tryParseJson(str) {
        try {
            if (typeof str === 'string') {
                return JSON.parse(str);
            }
        } catch (e) {
            // console.error(e);
        }
        return str;
    }

    function tryDecode(str) {
        if (str instanceof ArrayBuffer) {
            str = $e.utf82str(new Uint8Array(str));
        }
        if (str instanceof Uint8Array) {
            str = $e.utf82str(str);
        }
        str = tryParseJson(str);
        return str;
    }

    function tryDecodeAsync(str, callback) {
        if (str instanceof ArrayBuffer) {
            str = $e.utf82str(new Uint8Array(str));
        }
        if (str instanceof Uint8Array) {
            str = $e.utf82str(str);
        }
        str = tryParseJson(str);
        callback && callback(str);
        // JSON.parseAsync(str, function (str) {
        //     callback && callback(str);
        // });
        return str;
    }

    return {
        get: get,
        getUniversal: getUniversal,
        post: post,
        dataURItoBlob: dataURItoBlob,
        getUrl: getUrl,
        compactParams: compactParams,
        decodeJson: decodeJson,
        tryParseJson: tryParseJson,
        tryDecode: tryDecode,
        tryDecodeAsync: tryDecodeAsync,
        ReqAsync: ReqAsync,
    };

});